/* << fastinsert report library >> PdfGrid.cpp  Nov 1, 2010
 *
 * 	http://code.google.com/p/libfir
 *
 * Copyright (c) 2005 Pavlo Drobov (drobov.pavel@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
#include <string.h>
#include "PdfGrid.h"


#define ZERO_X 		14.0f
#define ZERO_Y 		(PdfBase::A4_hight - 28.0f)
#define ZERO_BOTTOM  20.0f
//#define V_LINE_MARGIN ZERO_X


#define LINE_GRAY_RATIO 	0.85f
#define LINE_WIDTH_RATIO 	0.4f
static const HPDF_UINT16 dashFactor [] = {2,2};
static const HPDF_UINT16 dashFactor2 [] = {4};


namespace dasco
{
namespace report
{


CellPdf::CellPdf(const ICell* iCell)
	:parent(iCell)
{
}

CellPdf::~CellPdf()
{
}

void CellPdf::render(PdfPage* page,float& indentH,float& indentV) const
{
	page->renderText(indentH, indentV,parent->asText());
}

GridPx::~GridPx()
{
	delete [] columnsWidth;
}


RowSetPdf::RowSetPdf(IRowSet* iRowSet)
	:parent(iRowSet)
{
}

RowSetPdf::~RowSetPdf()
{
}

unsigned RowSetPdf::calculateSymbol()
{
	unsigned rc = 0;
	iteratorICell rabbit = parent->getIterator();

	while(rabbit)
	{
  		rc += rabbit->getLength();
  		rabbit++;
	}

	return rc;
}

float RowSetPdf::render(PdfPage* page,float& indentH_begin,float& indentV,const GridPx& px) const
{
	renderLinePx pxLinePx;
	pxLinePx.gray = LINE_GRAY_RATIO;
	pxLinePx.brushWidth = LINE_WIDTH_RATIO;
	pxLinePx.dashFactor = dashFactor;


	float * rabbitColumnWidth = px.columnsWidth;
	float indentH = indentH_begin;
	iteratorICell rabbitH = parent->getIterator();
	while (rabbitH)
	{
		CellPdf(rabbitH).render(page,indentH,indentV);
		indentH += *rabbitColumnWidth++;
		rabbitH++;
	}


	pxLinePx.srcX = indentH_begin;
	pxLinePx.srcY = pxLinePx.dstY = indentV - px.fontSize/7.0f;
	pxLinePx.dstX = indentH;

	page->renderLine(&pxLinePx);

	return px.stepV;
}


bool RowSetPdf::calculateGridForFontSize(const PdfBase* pdfBase
									 ,__u16* grid
									 ,const float fontSize
									 ,const float *widthOfPartsPage) const
{
	bool rc = true;
	unsigned measureText;
	float *widthRabbit = (float*)widthOfPartsPage;
	__u16 *  gridRabbit = grid;

	//DEBUG_MSG("==> ######try for fornsize: " << fontSize);
	iteratorICell rabbit = parent->getIterator();
	while(rabbit && (widthOfPartsPage ? rc : true))
	{
		measureText =  pdfBase->measureText(
				 rabbit->asText()
				,fontSize
				,(widthOfPartsPage ? (*widthRabbit - fontSize) : PdfBase::A4_width -2*ZERO_X)
				);

		//set max measure
		if(measureText  > *gridRabbit)
			*gridRabbit = measureText;


		//set false flag if text not complete show in width
		rc = rabbit->getLength() == measureText;


	//	if(widthOfPartsPage)
	//	{
	//		DEBUG_MSG("==> txt: " <<  rabbit->asText() << " ; rc:" << rc);
	//		DEBUG_MSG("==> length: " <<  rabbit->getLength() << " measureText: " << measureText << " ; width:  " << *widthRabbit);
	//	}


		if (widthOfPartsPage)
			widthRabbit++;
		rabbit++;
		gridRabbit++ ;
	}

	return rc;
}

RowSetPdf * RowSetPdf::rowSetPdfFactory(IRowSet* iRowSet)
{
	return dynamic_cast<IRowSetHeader*>(iRowSet) ?
			new RowSetHeaderPdf(iRowSet) : new RowSetPdf(iRowSet);
}


////////////////////
RowSetHeaderPdf::RowSetHeaderPdf(IRowSet* iRowSet)
	:RowSetPdf(iRowSet)
{
}

RowSetHeaderPdf::~RowSetHeaderPdf()
{
}


//make zebra for header
float RowSetHeaderPdf::render(PdfPage* page,float& indentH_begin,float& indentV,const GridPx& px) const
{
/*
	const char sep[] = "\\/:;=- ";
	char * phrase, *brkb;
	unsigned mm = 0;


	iteratorICell rabbitHH = parent->getIterator();
	while (rabbitHH)
	{
		char * str = (char*)rabbitHH->asText();
		header hdr;
	       for (phrase = ::strtok_r(str, sep, &brkb);
	              phrase;
	              phrase = ::strtok_r(NULL, sep, &brkb))
	         {
	             DEBUG_MSG("BLA: " << phrase);
	             mm = MAX(mm,::strlen(phrase));
	         }
		rabbitHH++;
	}

	DEBUG_MSG("MM: " << mm );

	float kV  = mm *1.0f ;
*/

	float kV  = 4.0f;

	renderZebraPx pxZebra;
	pxZebra.gray = LINE_GRAY_RATIO;
	pxZebra.brushWidth = 0.3f;
	pxZebra.dashFactor = dashFactor;

	pxZebra.srcX = ZERO_X;
	pxZebra.hight  = kV* px.stepV;
	pxZebra.srcY = indentV + px.fontSize/2.0f - pxZebra.hight ;
	pxZebra.width = PdfBase::A4_width - 2*ZERO_X;
	pxZebra.step = px.fontSize /3;

	//DEBUG_MSG(pxZebra);
	page->renderZebra(&pxZebra);


	float * rabbitColumnWidth = px.columnsWidth;
	float indentH = indentH_begin;
	iteratorICell rabbitH = parent->getIterator();
	while (rabbitH)
	{
		page->beginText();

		matrix m(indentH + *rabbitColumnWidth/2.0f  //+ px.fontSize * 1.5f
				,indentV + px.fontSize/2.0f - pxZebra.hight + px.stepV/7.0f
				);
		m.stretchOutH(1.4f);
		m.stretchOutW(1.4f);

		page->setTextMatrix(&m);
		page->showText(rabbitH->asText());
		page->endText();

		rabbitH++;
		indentH += *rabbitColumnWidth++;
	}


	return pxZebra.hight + px.stepV;
}


GridPdf::GridPdf(IGrid* iGrid)
	:parent(iGrid)
	,fistPageThisReport(NULL)
	,fontFinding(false)
{
	//PdfPage * page = getNewPage();
}

GridPdf::~GridPdf()
{

}


void GridPdf::calculateGridPx(PdfBase* pdfBase,GridPx* px) const
{
	px->columnCnt = 0;

	iteratorIRowSet rabbit = parent->getIterator();
	while(rabbit)
	{
		const __u16 columnCnt = rabbit->cellQuantity();
		if (px->columnCnt < columnCnt)
				px->columnCnt = columnCnt;
		rabbit++;
	}
	DEBUG_MSG("MAX column: " <<  px->columnCnt);

	//find max column widt for this grid
	__u16  *maxColumnWidthRabbit, *maxColumnWidth, sumMaxColumns = 0;
	//buffer for max column width
	maxColumnWidthRabbit = maxColumnWidth = new __u16[px->columnCnt];
	::memset(maxColumnWidth,0,px->columnCnt * sizeof(__u16));

	rabbit = parent->getIterator();
	while(rabbit)
	{
		RowSetPdf(rabbit).calculateGridForFontSize(pdfBase,maxColumnWidth,MAYBEFONTSIZE_MIN);
		rabbit++;
	}

	//count max width for all
	maxColumnWidthRabbit = maxColumnWidth;
	while(maxColumnWidthRabbit - maxColumnWidth <  px->columnCnt)
		sumMaxColumns += *maxColumnWidthRabbit++;


	DEBUG_MSG("sumMaxColumns: " << sumMaxColumns);


	float * mayBeColumnWidthRabbit;
	mayBeColumnWidthRabbit = px->columnsWidth = new float[px->columnCnt];

	//count ratio factor for column
	maxColumnWidthRabbit = maxColumnWidth;
	while(maxColumnWidthRabbit - maxColumnWidth <  px->columnCnt)
		*mayBeColumnWidthRabbit++ = (PdfBase::A4_width -2*ZERO_X) *  (*maxColumnWidthRabbit++) / sumMaxColumns;

	//check max font size for this grid
	px->fontSize = MAYBEFONTSIZE_MAX + 0.5;
	while(px->fontSize >= MAYBEFONTSIZE_MIN && !fontFinding)
	{
		px->fontSize -= 0.5f;
		rabbit = parent->getIterator();
		//DEBUG_MSG("@@@@@find for font " << px->fontSize);
		while(rabbit)
		{
			fontFinding = RowSetPdf(rabbit).calculateGridForFontSize(pdfBase,maxColumnWidth,px->fontSize,px->columnsWidth);
			//DEBUG_MSG("\t +++++++++" << fontFinding);
			if (!fontFinding)
			{
				DEBUG_MSG("$$$ BREAK " << (bool)!fontFinding);
				break;
			}
			rabbit++;
		}
	};


	DEBUG_MSG("@@@@@font has been find: "  << px->fontSize << " ; fontfinding: " << fontFinding);

	//count page

	px->stepV = px->fontSize * 1.3f;

	delete [] maxColumnWidth;
}



void GridPdf::render(PdfBase* pdfBase)
{
	//outline
	if(parent->asText())
		outline = pdfBase->createOnline(parent->asText(),pdfBase->root);
	else
		outline = pdfBase->createOnline("Root",pdfBase->root);


    //calculate size of grid
    GridPx px;
    calculateGridPx(pdfBase,&px);

    renderLinePx pxLineV;
	pxLineV.gray = LINE_GRAY_RATIO;
	pxLineV.brushWidth = LINE_WIDTH_RATIO;
	pxLineV.dashFactor = dashFactor;
	pxLineV.srcY = ZERO_Y;
	pxLineV.dstY = ZERO_BOTTOM;


    PdfPage * page = NULL; //getNewPage();

	float indentV = ZERO_Y, indentH = ZERO_X;

	iteratorIRowSet rabbitV = parent->getIterator();
	while (rabbitV)
	{
		indentH = ZERO_X;

		if(!page  || indentV <= ZERO_BOTTOM)
		{
			page = getNewPage(pdfBase);
			//DEBUG_MSG("!!!!MAY BE NEW PAGE!! ");
			page->setFontAndSize(pdfBase->font, px.fontSize);
			indentV = ZERO_Y;
		}

		pxLineV.srcY = pxLineV.dstY = indentV + px.fontSize/2;

		RowSetPdf * rowSet = RowSetPdf::rowSetPdfFactory(rabbitV);
		indentV -= rowSet->render(page,indentH,indentV,px);
		delete rowSet;

		pxLineV.dstY = indentV + px.fontSize/2;


		//render V line
		//if I have normal font size -> minus 1 position
	//	DEBUG_MSG("FONT FINDING: " << fontFinding << " ; font size: " << px.fontSize);
		pxLineV.dstX = pxLineV.srcX = ZERO_X - (fontFinding ? px.fontSize : 0.0f);
		float * midgeV = px.columnsWidth;
		while (midgeV - px.columnsWidth  < px.columnCnt -1)
		{
			pxLineV.dstX = pxLineV.srcX +=  *midgeV++;
			//DEBUG_MSG("RENDER lineV x: " << pxLineV.dstX);
			page->renderLine(&pxLineV);
		}


		if(rabbitV->asText())
		{
			HPDF_Outline outlineThis =  pdfBase->createOnline(rabbitV->asText(),outline);

			//set distination for bookmark (outline)
			HPDF_Destination dst = page->createDestination();
			::HPDF_Destination_SetXYZ(dst, indentH,indentV+ px.stepV, 1);
			::HPDF_Outline_SetDestination(outlineThis, dst);
			::HPDF_Outline_SetOpened(outlineThis,false);
		}

		rabbitV++;
	}
}

PdfPage* GridPdf::getNewPage(PdfBase* pdfBase)
{
	PdfPage * page = pdfBase->getPage(); //get first page
	if (!fistPageThisReport)
	{
		fistPageThisReport = page;

		//set distination for bookmark (outline)
		HPDF_Destination dst = page->createDestination();
		::HPDF_Destination_SetXYZ(dst, 0, page->getHeight(), 1);
		::HPDF_Outline_SetDestination(outline, dst);
		::HPDF_Outline_SetOpened(outline,false);
	}
    return page;
}


}} //end namespace dasco::report



/*
/////////// C API ////////////////
extern "C"
{

//#define OUTLINE_CHECK(outline)  (outline ? (strlen(outline) > 1 ? outline : NULL) : NULL)
#define OUTLINE_CHECK(outline)  outline

using namespace dasco::report;


typedef RowSet			* RowSetHandle;
typedef GridBase		* GridBaseHandle;

extern HANDLE getReport();


DLL_EXPORT HANDLE getNewGrid(const char* outline)
{
	return  new GridBase((PdfBase*) getReport(), OUTLINE_CHECK(outline));
}

DLL_EXPORT void releaseGrid(HANDLE gridBaseHandle)
{
	DEBUG_MSG("releaseGrid grid: "  << (int)gridBaseHandle);
	delete (GridBaseHandle)gridBaseHandle;
}


DLL_EXPORT void renderGrid(HANDLE gridBaseHandle)
{
	DEBUG_MSG("render grid: "  << (int)gridBaseHandle);
	((GridBaseHandle)gridBaseHandle)->render();
}


DLL_EXPORT HANDLE getNewRowSet(const char* outline)
{
	  return  new RowSet(OUTLINE_CHECK(outline));
}

DLL_EXPORT HANDLE getNewRowSetHeader(const char* outline)
{
	  return  new RowSetHeader(OUTLINE_CHECK(outline));
}


DLL_EXPORT void addColumnText(HANDLE rowSet, const char* txt)
{
	if(rowSet)
		((RowSetHandle)rowSet)->addColumn(txt);
}

DLL_EXPORT void addColumnInt(HANDLE rowSet, int value)
{
	if(rowSet)
		((RowSetHandle)rowSet)->addColumn(value);
}

DLL_EXPORT void addColumnFloat(HANDLE rowSet, float value)
{
	if(rowSet)
		((RowSetHandle)rowSet)->addColumn(value);
}

DLL_EXPORT void addRowSet(HANDLE grid, HANDLE rowSet)
{
	if(grid)
		((GridBaseHandle)grid)->addRow((RowSetHandle)rowSet);
}

} //end extern "C"
*/
